<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        /**
         * 3. 收集谁 -- 收集依赖
         * 依赖是谁：所有使用这个属性的地方，可能是模版/Watcher
         * 因为使用变量的地方可能会很多，所以需要一个中介，先通知中介，再去通知其他地方
         * **/

        /* 
            如果是this.$watch
            vm.$watch('data.a.b.c', (newval) => {
                console.log(newVal)
            })
        
            如何实现上述功能？其实就是侦听器的原理
            - 上述功能是当data.a.b.c属性变化时，要执行的函数操作，=> 本质和变量变化，通知对应的模版值更新是一致的
            - 把该watcher实例添加到data.a.b.c对应的依赖中即可
        */

        class Watcher {
            constructor (vm, expOrfn, cb) {
                this.vm = vm
                this.getter = parsePath(expOrfn)
                this.cb = cb
                this.value = this.get()
            }
            
            get () {
                /* *
                    当前的watcher实例给window.target => 调用getter方法，收集依赖 push到dep数组 收集了依赖
                * */
                window.target = this
                let value = this.getter.call(this.vm, this.vm)
                window.target = undefined
                return value
            }

            /* **
                data.a.b.c的值发生变化 => 所有依赖项循环调用update => 触发get方法 => 执行cb回调函数，就是(newVal, oldVal) => {} 
            */
            update () {
                const oldValue = this.value
                // 拿到getter里面最新的值
                this.value = this.get()
                this.cb.call(this.vm, this.value, oldValue)
            }

        }


        /* 
            parsePath的作用是处理对象的深层次属性访问，比如data: {a: {b: {c:}}}要访问data.a.b.c，当把这个传递给watch函数时
            先访问obj[a]，然后obj = obj[a] 再访问obj[a][b] 再是obj[a][b][c]
        */
        const bailRE = /[^\w.$]/ // 匹配非法字符
        function parsePath (path) {
            // watch监听的要么是一个对象obj，要么必须是a.b这样的
            if (bailRE.test(path)) {
                return
            }
            let segments = path.split('.')
            return function (obj) {
                for (let i = 0; i < segments.length; i++) {
                    if (!obj) return
                    obj = obj[segments[i]]
                }
                return obj
            } 
        }
        
        class Dep {
            constructor () {
                this.subs = []
            }
            addSub (sub) {
                this.subs.push(sub)
            }
            removeSub (sub) {
                this.remove(this.subs, sub)
            }
            depend (sub) {
                // 啥用？
                if (window.target) {
                    this.addSub(window.target)
                }
            }
            notify (subs) {
                // 通知依赖执行
                for (let i = 0; i < subs.length; i++) {
                    subs[i].update()
                }
            }
        }

        // 移除函数
        function remove (array, item) {
            if (array.length) {
                let index = array.indexOf(item)
                if (index > -1) {
                    array.splice(index, 1)
                }
            }
        }

        // 改造该函数
        let dep = new Dep()
        function defineReactive (data, key, val) {
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    // 存储依赖
                    dep.depend()
                    return val
                },
                set: function (newVal) {
                    if (val === newVal) {
                        return
                    }
                    // 通知依赖
                    dep.notify()
                    val = newVal
                }
            })
        }
        let xhg = {
            height: 180
        }

        defineReactive(xhg, 'height', 190)
    </script>
</body>
</html>
